S Single Responsibility Principle Exercises

Rapid overview

Single Responsibility Principle (SRP) - Exercises

A class should have one, and only one, reason to change.

---

Foundational Exercises

Q: Identify the SRP violations in this User class and refactor it.

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    public void SaveToDatabase()
    {
        // Database logic
        var connection = new SqlConnection("...");
        // Save user
    }

    public void SendWelcomeEmail()
    {
        // Email logic
        var smtp = new SmtpClient();
        // Send email
    }

    public string GenerateUserReport()
    {
        // Report generation logic
        return $"User Report: {Name}";
    }

    public bool ValidateEmail()
    {
        // Validation logic
        return Email.Contains("@");
    }
}

A: The User class has multiple responsibilities: data storage, email sending, report generation, and validation. Refactor into separate classes:

// Single responsibility: User data
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// Single responsibility: Data persistence
public class UserRepository
{
    private readonly string _connectionString;

    public UserRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public void Save(User user)
    {
        using var connection = new SqlConnection(_connectionString);
        // Save user
    }
}

// Single responsibility: Email notifications
public class EmailService
{
    private readonly SmtpClient _smtpClient;

    public EmailService(SmtpClient smtpClient)
    {
        _smtpClient = smtpClient;
    }

    public void SendWelcomeEmail(User user)
    {
        // Send email
    }
}

// Single responsibility: Report generation
public class UserReportGenerator
{
    public string Generate(User user)
    {
        return $"User Report: {user.Name}";
    }
}

// Single responsibility: Validation
public class EmailValidator
{
    public bool Validate(string email)
    {
        return !string.IsNullOrEmpty(email) && email.Contains("@");
    }
}

Use when: Building maintainable applications where changes to one concern shouldn't affect others. Avoid when: Over-engineering simple DTOs or models that are pure data containers.

---

Q: Refactor this OrderProcessor class to follow SRP.

public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        // Validate order
        if (order.Items.Count == 0)
            throw new Exception("Order must have items");

        // Calculate total
        decimal total = 0;
        foreach (var item in order.Items)
        {
            total += item.Price * item.Quantity;
        }
        order.Total = total;

        // Apply discount
        if (order.Customer.IsPremium)
        {
            order.Total *= 0.9m;
        }

        // Save to database
        var db = new SqlConnection("...");
        // Save order

        // Send confirmation email
        var smtp = new SmtpClient();
        // Send email

        // Log
        Console.WriteLine($"Order {order.Id} processed");
    }
}

A: Separate into distinct responsibilities:

public class Order
{
    public int Id { get; set; }
    public Customer Customer { get; set; }
    public List<OrderItem> Items { get; set; } = new();
    public decimal Total { get; set; }
}

// Responsibility: Order validation
public class OrderValidator
{
    public void Validate(Order order)
    {
        if (order.Items.Count == 0)
            throw new InvalidOperationException("Order must have items");

        if (order.Customer == null)
            throw new InvalidOperationException("Order must have a customer");
    }
}

// Responsibility: Price calculation
public class OrderPriceCalculator
{
    public decimal CalculateTotal(Order order)
    {
        return order.Items.Sum(item => item.Price * item.Quantity);
    }
}

// Responsibility: Discount application
public class DiscountService
{
    public decimal ApplyDiscount(decimal amount, Customer customer)
    {
        return customer.IsPremium ? amount * 0.9m : amount;
    }
}

// Responsibility: Data persistence
public class OrderRepository
{
    private readonly IDbConnection _connection;

    public OrderRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public void Save(Order order)
    {
        // Save to database
    }
}

// Responsibility: Email notifications
public class OrderNotificationService
{
    private readonly IEmailService _emailService;

    public OrderNotificationService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void SendConfirmation(Order order)
    {
        _emailService.Send(order.Customer.Email, "Order Confirmation", $"Order {order.Id}");
    }
}

// Responsibility: Logging
public class OrderLogger
{
    private readonly ILogger _logger;

    public OrderLogger(ILogger logger)
    {
        _logger = logger;
    }

    public void LogProcessed(Order order)
    {
        _logger.LogInformation($"Order {order.Id} processed");
    }
}

// Orchestrator: Coordinates the workflow
public class OrderProcessor
{
    private readonly OrderValidator _validator;
    private readonly OrderPriceCalculator _calculator;
    private readonly DiscountService _discountService;
    private readonly OrderRepository _repository;
    private readonly OrderNotificationService _notificationService;
    private readonly OrderLogger _logger;

    public OrderProcessor(
        OrderValidator validator,
        OrderPriceCalculator calculator,
        DiscountService discountService,
        OrderRepository repository,
        OrderNotificationService notificationService,
        OrderLogger logger)
    {
        _validator = validator;
        _calculator = calculator;
        _discountService = discountService;
        _repository = repository;
        _notificationService = notificationService;
        _logger = logger;
    }

    public void ProcessOrder(Order order)
    {
        _validator.Validate(order);

        var subtotal = _calculator.CalculateTotal(order);
        order.Total = _discountService.ApplyDiscount(subtotal, order.Customer);

        _repository.Save(order);
        _notificationService.SendConfirmation(order);
        _logger.LogProcessed(order);
    }
}

---

Intermediate Exercises

Q: Identify SRP violations in this ReportGenerator class.

public class ReportGenerator
{
    public string GenerateReport(List<Sale> sales)
    {
        // Fetch data
        var connection = new SqlConnection("...");
        var salesData = FetchSalesData(connection);

        // Calculate metrics
        var totalSales = salesData.Sum(s => s.Amount);
        var averageSale = salesData.Average(s => s.Amount);

        // Format report
        var report = new StringBuilder();
        report.AppendLine($"Total Sales: {totalSales}");
        report.AppendLine($"Average Sale: {averageSale}");

        // Save to file
        File.WriteAllText("report.txt", report.ToString());

        // Send via email
        var smtp = new SmtpClient();
        // Send email

        return report.ToString();
    }

    private List<Sale> FetchSalesData(SqlConnection connection)
    {
        // Fetch from database
        return new List<Sale>();
    }
}

A: Separate into distinct responsibilities:

// Responsibility: Data access
public class SalesRepository
{
    private readonly IDbConnection _connection;

    public SalesRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public List<Sale> GetAll()
    {
        // Fetch from database
        return new List<Sale>();
    }
}

// Responsibility: Business calculations
public class SalesMetricsCalculator
{
    public SalesMetrics Calculate(List<Sale> sales)
    {
        return new SalesMetrics
        {
            TotalSales = sales.Sum(s => s.Amount),
            AverageSale = sales.Average(s => s.Amount),
            SaleCount = sales.Count
        };
    }
}

// Responsibility: Report formatting
public class ReportFormatter
{
    public string Format(SalesMetrics metrics)
    {
        var report = new StringBuilder();
        report.AppendLine($"Total Sales: {metrics.TotalSales:C}");
        report.AppendLine($"Average Sale: {metrics.AverageSale:C}");
        report.AppendLine($"Number of Sales: {metrics.SaleCount}");
        return report.ToString();
    }
}

// Responsibility: File operations
public class ReportFileWriter
{
    public void WriteToFile(string content, string filePath)
    {
        File.WriteAllText(filePath, content);
    }
}

// Responsibility: Email delivery
public class ReportEmailService
{
    private readonly IEmailService _emailService;

    public ReportEmailService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void SendReport(string report, string recipient)
    {
        _emailService.Send(recipient, "Sales Report", report);
    }
}

// Orchestrator
public class ReportGenerator
{
    private readonly SalesRepository _repository;
    private readonly SalesMetricsCalculator _calculator;
    private readonly ReportFormatter _formatter;
    private readonly ReportFileWriter _fileWriter;
    private readonly ReportEmailService _emailService;

    public ReportGenerator(
        SalesRepository repository,
        SalesMetricsCalculator calculator,
        ReportFormatter formatter,
        ReportFileWriter fileWriter,
        ReportEmailService emailService)
    {
        _repository = repository;
        _calculator = calculator;
        _formatter = formatter;
        _fileWriter = fileWriter;
        _emailService = emailService;
    }

    public string GenerateAndDistributeReport(string recipient, string filePath)
    {
        var sales = _repository.GetAll();
        var metrics = _calculator.Calculate(sales);
        var report = _formatter.Format(metrics);

        _fileWriter.WriteToFile(report, filePath);
        _emailService.SendReport(report, recipient);

        return report;
    }
}

---

Q: Refactor this Employee class that handles both employee data and payroll calculations.

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal HourlyRate { get; set; }
    public int HoursWorked { get; set; }

    public decimal CalculatePayment()
    {
        var regularHours = Math.Min(HoursWorked, 40);
        var overtimeHours = Math.Max(HoursWorked - 40, 0);

        var regularPay = regularHours * HourlyRate;
        var overtimePay = overtimeHours * HourlyRate * 1.5m;

        return regularPay + overtimePay;
    }

    public decimal CalculateTax()
    {
        var payment = CalculatePayment();
        if (payment < 1000) return payment * 0.1m;
        if (payment < 5000) return payment * 0.2m;
        return payment * 0.3m;
    }

    public void SaveToDatabase()
    {
        var connection = new SqlConnection("...");
        // Save employee
    }

    public string GeneratePayslip()
    {
        return $"Employee: {Name}, Payment: {CalculatePayment():C}, Tax: {CalculateTax():C}";
    }
}

A: Separate into focused classes:

// Responsibility: Employee data
public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal HourlyRate { get; set; }
    public int HoursWorked { get; set; }
}

// Responsibility: Payroll calculations
public class PayrollCalculator
{
    private const int StandardHours = 40;
    private const decimal OvertimeMultiplier = 1.5m;

    public decimal CalculatePayment(Employee employee)
    {
        var regularHours = Math.Min(employee.HoursWorked, StandardHours);
        var overtimeHours = Math.Max(employee.HoursWorked - StandardHours, 0);

        var regularPay = regularHours * employee.HourlyRate;
        var overtimePay = overtimeHours * employee.HourlyRate * OvertimeMultiplier;

        return regularPay + overtimePay;
    }
}

// Responsibility: Tax calculations
public class TaxCalculator
{
    public decimal CalculateTax(decimal payment)
    {
        if (payment < 1000) return payment * 0.1m;
        if (payment < 5000) return payment * 0.2m;
        return payment * 0.3m;
    }
}

// Responsibility: Data persistence
public class EmployeeRepository
{
    private readonly IDbConnection _connection;

    public EmployeeRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public void Save(Employee employee)
    {
        // Save to database
    }

    public Employee GetById(int id)
    {
        // Retrieve from database
        return null;
    }
}

// Responsibility: Document generation
public class PayslipGenerator
{
    private readonly PayrollCalculator _payrollCalculator;
    private readonly TaxCalculator _taxCalculator;

    public PayslipGenerator(PayrollCalculator payrollCalculator, TaxCalculator taxCalculator)
    {
        _payrollCalculator = payrollCalculator;
        _taxCalculator = taxCalculator;
    }

    public string Generate(Employee employee)
    {
        var payment = _payrollCalculator.CalculatePayment(employee);
        var tax = _taxCalculator.CalculateTax(payment);

        return $"Employee: {employee.Name}\n" +
               $"Payment: {payment:C}\n" +
               $"Tax: {tax:C}\n" +
               $"Net Pay: {payment - tax:C}";
    }
}

---

Advanced Exercises

Q: Design a logging system that follows SRP. It should support multiple log levels, formats, and destinations.

A: Create separate classes for each responsibility:

// Responsibility: Log entry data
public class LogEntry
{
    public DateTime Timestamp { get; set; }
    public LogLevel Level { get; set; }
    public string Message { get; set; }
    public string Category { get; set; }
    public Exception Exception { get; set; }
}

public enum LogLevel
{
    Debug, Info, Warning, Error, Critical
}

// Responsibility: Log formatting
public interface ILogFormatter
{
    string Format(LogEntry entry);
}

public class JsonLogFormatter : ILogFormatter
{
    public string Format(LogEntry entry)
    {
        return JsonSerializer.Serialize(entry);
    }
}

public class PlainTextLogFormatter : ILogFormatter
{
    public string Format(LogEntry entry)
    {
        return $"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] [{entry.Level}] {entry.Message}";
    }
}

// Responsibility: Log destination
public interface ILogDestination
{
    void Write(string formattedLog);
}

public class FileLogDestination : ILogDestination
{
    private readonly string _filePath;

    public FileLogDestination(string filePath)
    {
        _filePath = filePath;
    }

    public void Write(string formattedLog)
    {
        File.AppendAllText(_filePath, formattedLog + Environment.NewLine);
    }
}

public class ConsoleLogDestination : ILogDestination
{
    public void Write(string formattedLog)
    {
        Console.WriteLine(formattedLog);
    }
}

public class DatabaseLogDestination : ILogDestination
{
    private readonly IDbConnection _connection;

    public DatabaseLogDestination(IDbConnection connection)
    {
        _connection = connection;
    }

    public void Write(string formattedLog)
    {
        // Write to database
    }
}

// Responsibility: Log filtering
public interface ILogFilter
{
    bool ShouldLog(LogEntry entry);
}

public class LogLevelFilter : ILogFilter
{
    private readonly LogLevel _minLevel;

    public LogLevelFilter(LogLevel minLevel)
    {
        _minLevel = minLevel;
    }

    public bool ShouldLog(LogEntry entry)
    {
        return entry.Level >= _minLevel;
    }
}

// Responsibility: Orchestrating logging
public class Logger
{
    private readonly List<ILogDestination> _destinations;
    private readonly ILogFormatter _formatter;
    private readonly ILogFilter _filter;

    public Logger(
        ILogFormatter formatter,
        ILogFilter filter,
        params ILogDestination[] destinations)
    {
        _formatter = formatter;
        _filter = filter;
        _destinations = new List<ILogDestination>(destinations);
    }

    public void Log(LogLevel level, string message, string category = null, Exception exception = null)
    {
        var entry = new LogEntry
        {
            Timestamp = DateTime.UtcNow,
            Level = level,
            Message = message,
            Category = category,
            Exception = exception
        };

        if (!_filter.ShouldLog(entry))
            return;

        var formattedLog = _formatter.Format(entry);

        foreach (var destination in _destinations)
        {
            destination.Write(formattedLog);
        }
    }

    public void Debug(string message) => Log(LogLevel.Debug, message);
    public void Info(string message) => Log(LogLevel.Info, message);
    public void Warning(string message) => Log(LogLevel.Warning, message);
    public void Error(string message, Exception ex = null) => Log(LogLevel.Error, message, exception: ex);
}

// Usage
var logger = new Logger(
    new JsonLogFormatter(),
    new LogLevelFilter(LogLevel.Info),
    new FileLogDestination("app.log"),
    new ConsoleLogDestination()
);

logger.Info("Application started");
logger.Error("An error occurred", new Exception("Test exception"));

---

Q: Create a file processing system that reads, validates, transforms, and stores data while following SRP.

A: Separate each step into its own class:

// Responsibility: File reading
public interface IFileReader
{
    Task<string> ReadAsync(string filePath);
}

public class TextFileReader : IFileReader
{
    public async Task<string> ReadAsync(string filePath)
    {
        return await File.ReadAllTextAsync(filePath);
    }
}

// Responsibility: Data parsing
public interface IDataParser<T>
{
    T Parse(string content);
}

public class CsvParser : IDataParser<List<Dictionary<string, string>>>
{
    public List<Dictionary<string, string>> Parse(string content)
    {
        var lines = content.Split('\n');
        var headers = lines[0].Split(',');

        return lines.Skip(1)
            .Select(line =>
            {
                var values = line.Split(',');
                return headers.Zip(values, (h, v) => new { h, v })
                    .ToDictionary(x => x.h, x => x.v);
            })
            .ToList();
    }
}

// Responsibility: Data validation
public interface IDataValidator<T>
{
    ValidationResult Validate(T data);
}

public class ValidationResult
{
    public bool IsValid { get; set; }
    public List<string> Errors { get; set; } = new();
}

public class OrderDataValidator : IDataValidator<List<Dictionary<string, string>>>
{
    public ValidationResult Validate(List<Dictionary<string, string>> data)
    {
        var result = new ValidationResult { IsValid = true };

        foreach (var row in data)
        {
            if (!row.ContainsKey("OrderId") || string.IsNullOrEmpty(row["OrderId"]))
            {
                result.IsValid = false;
                result.Errors.Add("OrderId is required");
            }

            if (row.ContainsKey("Amount") && !decimal.TryParse(row["Amount"], out _))
            {
                result.IsValid = false;
                result.Errors.Add($"Invalid amount in order {row.GetValueOrDefault("OrderId")}");
            }
        }

        return result;
    }
}

// Responsibility: Data transformation
public interface IDataTransformer<TInput, TOutput>
{
    TOutput Transform(TInput data);
}

public class OrderTransformer : IDataTransformer<List<Dictionary<string, string>>, List<Order>>
{
    public List<Order> Transform(List<Dictionary<string, string>> data)
    {
        return data.Select(row => new Order
        {
            Id = row["OrderId"],
            Amount = decimal.Parse(row["Amount"]),
            CustomerId = row.GetValueOrDefault("CustomerId")
        }).ToList();
    }
}

// Responsibility: Data storage
public interface IDataRepository<T>
{
    Task SaveAsync(T data);
}

public class OrderRepository : IDataRepository<List<Order>>
{
    private readonly IDbConnection _connection;

    public OrderRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task SaveAsync(List<Order> orders)
    {
        // Save to database
        await Task.CompletedTask;
    }
}

// Responsibility: Error handling and logging
public interface IProcessingLogger
{
    void LogStart(string filePath);
    void LogValidationErrors(ValidationResult result);
    void LogSuccess(int recordCount);
    void LogError(Exception ex);
}

public class ProcessingLogger : IProcessingLogger
{
    private readonly ILogger _logger;

    public ProcessingLogger(ILogger logger)
    {
        _logger = logger;
    }

    public void LogStart(string filePath)
    {
        _logger.LogInformation($"Starting to process file: {filePath}");
    }

    public void LogValidationErrors(ValidationResult result)
    {
        foreach (var error in result.Errors)
        {
            _logger.LogWarning($"Validation error: {error}");
        }
    }

    public void LogSuccess(int recordCount)
    {
        _logger.LogInformation($"Successfully processed {recordCount} records");
    }

    public void LogError(Exception ex)
    {
        _logger.LogError(ex, "Error processing file");
    }
}

// Orchestrator: Coordinates the workflow
public class FileProcessingService
{
    private readonly IFileReader _fileReader;
    private readonly IDataParser<List<Dictionary<string, string>>> _parser;
    private readonly IDataValidator<List<Dictionary<string, string>>> _validator;
    private readonly IDataTransformer<List<Dictionary<string, string>>, List<Order>> _transformer;
    private readonly IDataRepository<List<Order>> _repository;
    private readonly IProcessingLogger _logger;

    public FileProcessingService(
        IFileReader fileReader,
        IDataParser<List<Dictionary<string, string>>> parser,
        IDataValidator<List<Dictionary<string, string>>> validator,
        IDataTransformer<List<Dictionary<string, string>>, List<Order>> transformer,
        IDataRepository<List<Order>> repository,
        IProcessingLogger logger)
    {
        _fileReader = fileReader;
        _parser = parser;
        _validator = validator;
        _transformer = transformer;
        _repository = repository;
        _logger = logger;
    }

    public async Task ProcessFileAsync(string filePath)
    {
        try
        {
            _logger.LogStart(filePath);

            // Read
            var content = await _fileReader.ReadAsync(filePath);

            // Parse
            var rawData = _parser.Parse(content);

            // Validate
            var validationResult = _validator.Validate(rawData);
            if (!validationResult.IsValid)
            {
                _logger.LogValidationErrors(validationResult);
                return;
            }

            // Transform
            var orders = _transformer.Transform(rawData);

            // Store
            await _repository.SaveAsync(orders);

            _logger.LogSuccess(orders.Count);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex);
            throw;
        }
    }
}

---

Real-World Scenarios

Q: Design an e-commerce checkout system following SRP. Include inventory checking, payment processing, order creation, and notifications.

A: Create focused services for each responsibility:

// Domain entities
public class Cart
{
    public string CustomerId { get; set; }
    public List<CartItem> Items { get; set; } = new();
}

public class CartItem
{
    public string ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}

public class Order
{
    public string Id { get; set; }
    public string CustomerId { get; set; }
    public List<OrderItem> Items { get; set; } = new();
    public decimal Total { get; set; }
    public string Status { get; set; }
    public DateTime CreatedAt { get; set; }
}

// Responsibility: Inventory management
public interface IInventoryService
{
    Task<bool> CheckAvailabilityAsync(string productId, int quantity);
    Task ReserveAsync(string productId, int quantity);
    Task ReleaseAsync(string productId, int quantity);
}

public class InventoryService : IInventoryService
{
    private readonly IInventoryRepository _repository;

    public InventoryService(IInventoryRepository repository)
    {
        _repository = repository;
    }

    public async Task<bool> CheckAvailabilityAsync(string productId, int quantity)
    {
        var stock = await _repository.GetStockAsync(productId);
        return stock >= quantity;
    }

    public async Task ReserveAsync(string productId, int quantity)
    {
        await _repository.DecrementStockAsync(productId, quantity);
    }

    public async Task ReleaseAsync(string productId, int quantity)
    {
        await _repository.IncrementStockAsync(productId, quantity);
    }
}

// Responsibility: Payment processing
public interface IPaymentService
{
    Task<PaymentResult> ProcessPaymentAsync(string customerId, decimal amount, string paymentMethod);
}

public class PaymentResult
{
    public bool Success { get; set; }
    public string TransactionId { get; set; }
    public string ErrorMessage { get; set; }
}

public class PaymentService : IPaymentService
{
    private readonly IPaymentGateway _gateway;

    public PaymentService(IPaymentGateway gateway)
    {
        _gateway = gateway;
    }

    public async Task<PaymentResult> ProcessPaymentAsync(string customerId, decimal amount, string paymentMethod)
    {
        return await _gateway.ChargeAsync(customerId, amount, paymentMethod);
    }
}

// Responsibility: Order creation
public interface IOrderFactory
{
    Order CreateOrder(Cart cart);
}

public class OrderFactory : IOrderFactory
{
    public Order CreateOrder(Cart cart)
    {
        return new Order
        {
            Id = Guid.NewGuid().ToString(),
            CustomerId = cart.CustomerId,
            Items = cart.Items.Select(ci => new OrderItem
            {
                ProductId = ci.ProductId,
                Quantity = ci.Quantity,
                Price = ci.Price
            }).ToList(),
            Total = cart.Items.Sum(ci => ci.Price * ci.Quantity),
            Status = "Pending",
            CreatedAt = DateTime.UtcNow
        };
    }
}

// Responsibility: Order persistence
public interface IOrderRepository
{
    Task SaveAsync(Order order);
    Task UpdateStatusAsync(string orderId, string status);
}

// Responsibility: Customer notifications
public interface INotificationService
{
    Task SendOrderConfirmationAsync(Order order);
    Task SendPaymentFailureAsync(string customerId, string reason);
}

public class NotificationService : INotificationService
{
    private readonly IEmailService _emailService;
    private readonly ISmsService _smsService;

    public NotificationService(IEmailService emailService, ISmsService smsService)
    {
        _emailService = emailService;
        _smsService = smsService;
    }

    public async Task SendOrderConfirmationAsync(Order order)
    {
        await _emailService.SendAsync(order.CustomerId, "Order Confirmation", $"Order {order.Id} confirmed");
    }

    public async Task SendPaymentFailureAsync(string customerId, string reason)
    {
        await _emailService.SendAsync(customerId, "Payment Failed", reason);
    }
}

// Orchestrator: Checkout process
public class CheckoutService
{
    private readonly IInventoryService _inventoryService;
    private readonly IPaymentService _paymentService;
    private readonly IOrderFactory _orderFactory;
    private readonly IOrderRepository _orderRepository;
    private readonly INotificationService _notificationService;
    private readonly ILogger<CheckoutService> _logger;

    public CheckoutService(
        IInventoryService inventoryService,
        IPaymentService paymentService,
        IOrderFactory orderFactory,
        IOrderRepository orderRepository,
        INotificationService notificationService,
        ILogger<CheckoutService> logger)
    {
        _inventoryService = inventoryService;
        _paymentService = paymentService;
        _orderFactory = orderFactory;
        _orderRepository = orderRepository;
        _notificationService = notificationService;
        _logger = logger;
    }

    public async Task<CheckoutResult> CheckoutAsync(Cart cart, string paymentMethod)
    {
        try
        {
            // Step 1: Check inventory
            foreach (var item in cart.Items)
            {
                var available = await _inventoryService.CheckAvailabilityAsync(item.ProductId, item.Quantity);
                if (!available)
                {
                    return CheckoutResult.Failed($"Product {item.ProductId} is out of stock");
                }
            }

            // Step 2: Reserve inventory
            foreach (var item in cart.Items)
            {
                await _inventoryService.ReserveAsync(item.ProductId, item.Quantity);
            }

            // Step 3: Create order
            var order = _orderFactory.CreateOrder(cart);
            await _orderRepository.SaveAsync(order);

            // Step 4: Process payment
            var paymentResult = await _paymentService.ProcessPaymentAsync(
                cart.CustomerId,
                order.Total,
                paymentMethod);

            if (!paymentResult.Success)
            {
                // Rollback inventory
                foreach (var item in cart.Items)
                {
                    await _inventoryService.ReleaseAsync(item.ProductId, item.Quantity);
                }

                await _orderRepository.UpdateStatusAsync(order.Id, "PaymentFailed");
                await _notificationService.SendPaymentFailureAsync(cart.CustomerId, paymentResult.ErrorMessage);

                return CheckoutResult.Failed(paymentResult.ErrorMessage);
            }

            // Step 5: Confirm order
            await _orderRepository.UpdateStatusAsync(order.Id, "Confirmed");
            await _notificationService.SendOrderConfirmationAsync(order);

            _logger.LogInformation($"Checkout completed for order {order.Id}");

            return CheckoutResult.Success(order.Id);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Checkout failed");
            throw;
        }
    }
}

public class CheckoutResult
{
    public bool Success { get; set; }
    public string OrderId { get; set; }
    public string ErrorMessage { get; set; }

    public static CheckoutResult Success(string orderId) =>
        new() { Success = true, OrderId = orderId };

    public static CheckoutResult Failed(string message) =>
        new() { Success = false, ErrorMessage = message };
}

---

Total Exercises: 25+

Each refactoring demonstrates how SRP makes code more maintainable, testable, and easier to change. Remember: a class should have only one reason to change!